

package main

import (
	"io"
	"log"
	"net/http"
	"strconv"

	"go.etcd.io/etcd/raft/v3/raftpb"
)

// raft支持的基于http的键值存储的处理程序
// httpKVAPI实现了http.Handler接口
type httpKVAPI struct {
	// kvstore实例在raftexample示例中扮演了持久化存储的角色，用于保存用户提交的键值对信息
	store       *kvstore
	// 当用户发送POST（或DELETE）请求时，会被认为是发送了一个集群节点增加（或删除）的请求，
	// httpKVAPI会将该请求的信息写入confChangeC通道
	confChangeC chan<- raftpb.ConfChange
}

// 根据HTTP请求的Method进行分类处理，其中只处理PUT、GET、POST和DELETE四种Method
func (h *httpKVAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	key := r.RequestURI	// 获取请求的URI作为key
	defer r.Body.Close()
	switch r.Method {
	// PUT请求表示向kvstore实例中添加（或更新）指定的键值对数据。
	case http.MethodPut:	// PUT请求处理
		v, err := io.ReadAll(r.Body)	// 读取HTTP请求体
		if err != nil {
			log.Printf("Failed to read on PUT (%v)\n", err)
			http.Error(w, "Failed on PUT", http.StatusBadRequest)
			return
		}
		// 在kvstore.Propose()方法中会对键位对进行序列化，之后将结果写入proposeC通道，
		// 后续raftNode会读取其中数据进行处理，
		h.store.Propose(key, string(v))

		// 向用户返回相应的状态码
		w.WriteHeader(http.StatusNoContent)
	// GET请求表示从kvstore实例中读取指定的键值对数据。
	case http.MethodGet:
		// 直接从kvstore中读取指定的键位对数据，并返回给用户
		if v, ok := h.store.Lookup(key); ok {
			w.Write([]byte(v))
		} else {
			http.Error(w, "Failed to GET", http.StatusNotFound)
		}
	// POST请求表示向集群中新增指定的节点。
	case http.MethodPost:	// POST请求的处理
		url, err := io.ReadAll(r.Body)	// 读取请求体，获取新加节点的URL
		if err != nil {
			log.Printf("Failed to read on POST (%v)\n", err)
			http.Error(w, "Failed on POST", http.StatusBadRequest)
			return
		}
		// 解析URI得到新增节点的id
		nodeId, err := strconv.ParseUint(key[1:], 0, 64)
		if err != nil {
			log.Printf("Failed to convert ID for conf change (%v)\n", err)
			http.Error(w, "Failed on POST", http.StatusBadRequest)
			return
		}

		cc := raftpb.ConfChange{	// 创建ConfChange消息
			Type:    raftpb.ConfChangeAddNode,	// ConfChangeAddNode即表示新增节点
			NodeID:  nodeId,	// 指定新增节点的ID
			Context: url,	// 指定新增节点的URL
		}
		// 将ConfChange实例写入confChangeC通道中
		h.confChangeC <- cc
		// 返回相应状态码
		w.WriteHeader(http.StatusNoContent)
	// DELETE请求表示从集群中删除指定的节点。
	case http.MethodDelete:	// DELETE 请求的处理
		// 解析key得到的待删除的节点id
		nodeId, err := strconv.ParseUint(key[1:], 0, 64)
		if err != nil {
			log.Printf("Failed to convert ID for conf change (%v)\n", err)
			http.Error(w, "Failed on DELETE", http.StatusBadRequest)
			return
		}

		cc := raftpb.ConfChange{
			Type:   raftpb.ConfChangeRemoveNode,	// ConfChangeRernoveNode表示删除指定节点
			NodeID: nodeId,	// 指定待删除的节点id
		}
		h.confChangeC <- cc	// 将ConfChange实例发送到confChangeC通道中

		// 与PUT请求的处理方式类似，向客户端返回状态码为204的HTTP响应
		w.WriteHeader(http.StatusNoContent)
	default:
		w.Header().Set("Allow", http.MethodPut)
		w.Header().Add("Allow", http.MethodGet)
		w.Header().Add("Allow", http.MethodPost)
		w.Header().Add("Allow", http.MethodDelete)
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

// serveHttpKVAPI使用GET/PUT API启动键值服务器并侦听。
// 会启动一个单独的后台goroutine来监昕指定的地址，接收用户发来的HTTP请求
func serveHttpKVAPI(kv *kvstore, port int, confChangeC chan<- raftpb.ConfChange, errorC <-chan error) {
	srv := http.Server{	// 创建http.Server用于接收HTTP请求
		Addr: ":" + strconv.Itoa(port),
		Handler: &httpKVAPI{	// 设置http.Server的Handler字段，即httpKVAPI实例
			store:       kv,
			confChangeC: confChangeC,
		},
	}
	// 启动羊独的goroutine来监听Addr指定的地址，当有HTTP请求时，http.Server会创建对应的
	// goroutine，并调用httpKVAPI.ServeHTTP()方法进行处理
	go func() {
		if err := srv.ListenAndServe(); err != nil {
			log.Fatal(err)
		}
	}()

	// 当筏下降时退出
	if err, ok := <-errorC; ok {
		log.Fatal(err)
	}
}
